home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Internet / FTP / Mirror2.3 / mm < prev    next >
Encoding:
Text File  |  1994-01-18  |  10.4 KB  |  504 lines

  1. #!/usr/bin/perl
  2. # Mirror Master.
  3. # Run several mirrors in parallel.
  4. #
  5. # By Lee McLoughlin <lmjm@doc.ic.ac.uk>
  6. #  You can do what you like with this except claim that you wrote it or
  7. #  give copies with changes not approved by Lee.  Neither Lee nor any other
  8. #  organisation can be held liable for any problems caused by the use or
  9. #  storage of this package.
  10. #
  11. # $Id: mm,v 2.3 1994/01/18 21:58:30 lmjm Exp lmjm $
  12. # $Log: mm,v $
  13. # Revision 2.3  1994/01/18  21:58:30  lmjm
  14. # Correct status check.
  15. #
  16. # Revision 2.2  1993/12/14  11:09:21  lmjm
  17. # Minor improvements.
  18. #
  19. # Revision 2.1  1993/06/28  15:21:28  lmjm
  20. # Full 2.1 release
  21. #
  22. #
  23.  
  24. # Args:
  25. # -opattern        - limit to site:packages matching pattern
  26. # -t            - ignore timers
  27. # -debug        - increase debugging level(-debug -debug =more debugging)
  28. # -s            - turn on process entry/exit debugging
  29.  
  30. # mm input looks like:
  31. # home=directory    - where to work from
  32. # max=N            - max. no. of parallel mirrors
  33. # mirror=command    - how to call mirror
  34. # skip=site:package    - skip this site:package when you come across it
  35. # cmd=command        - Run this command now.
  36. # cmdin=command        - Run this command and use its output as mm input
  37. # site:package min-restart-last-ok min-restart-last-notok mirror args
  38. # EXIT            - skip rest of current file
  39.  
  40.  
  41. # Defaults
  42. # Max mirrors to run at the same time
  43. $max = 6;
  44.  
  45. # In $mirror the $args, $package and $site fields are replaced with
  46. # fields from the package entry in the mm input files.
  47. # $pkg is the package number fixed up to replace characters likely to give
  48. # grief under unix.
  49. # This expects the directory logs to already exist.
  50. $mirror = "exec ./mirror \$args -p'\$package' packages/\$site > logs/\$site:\$pkg 2>&1";
  51.  
  52. $status_file = 'mm.status';
  53.  
  54. # used as a file handle.
  55. $fileno = 'fd00';
  56.  
  57. $running = 0;
  58.  
  59. # Really should share these properly with mirror
  60. # "#defines" for the above
  61. $exit_xfers = 16;  # Add this to the exit code to show xfers took place
  62. $exit_ok = 0;
  63. $exit_fail = 1;
  64. $exit_fail_noconnect = 2;
  65.  
  66. # Used in the status file to mark a site:package locked
  67. $locked = 'l';
  68. $unlocked = 'u';
  69.  
  70. $secs_per_hour = 60 * 60;
  71.  
  72. # Hopefully we have flock.
  73. $can_flock = 1;
  74.  
  75. # Parse arguments
  76. while( $#ARGV >= 0 ){
  77.     local( $arg ) = shift;
  78.  
  79.     # only both with -flag's
  80.     if( $arg !~ /^-/ ){
  81.         unshift( ARGV, $arg );
  82.         last;
  83.     }
  84.  
  85.     if( $arg =~ /-o(.*)/ ){
  86.         # Only for these site:packages
  87.         $only = $1;
  88.     }
  89.     elsif( $arg =~ /-t/ ){
  90.         $ignore_timers = 1;
  91.     }
  92.     elsif( $arg =~ /-debug/ ){
  93.         $debug++;
  94.         $| = 1;
  95.     }
  96.     elsif( $arg =~ /-s/ ){
  97.         $status_debug = 1;
  98.         $| = 1;
  99.     }
  100.     else {
  101.         # Pass any unknown args down to mirror
  102.         $extra_args .= ' ' . $arg;
  103.     }
  104. }
  105.  
  106. $0 = "mm";
  107.  
  108. @ARGV = ('-') if ! @ARGV;
  109. while( $#ARGV >= 0 ){
  110.     &parse_file( shift );
  111. }
  112.  
  113. &wait_till_done( 0 );
  114.  
  115. sub parse_file
  116. {
  117.     local( $file ) = @_;
  118.     local( $fd, $closeit );
  119.     
  120.     if( $debug > 1){
  121.         print "parse_file( $file )\n";
  122.     }
  123.  
  124.     if( $file eq '-' ){
  125.         $fd = 'STDIN';
  126.         $closeit = 0;
  127.     }
  128.     else {
  129.         $fd = $fileno++;
  130.         if( ! open( $fd, $file ) ){
  131.             die "Cannot open $file";
  132.         }
  133.         $closeit = 1;
  134.     }
  135.         
  136.     while( <$fd> ){
  137. #        print "$fd: ",$_ if $debug;
  138.         next if /^#/ || /^\s*$/;
  139.         
  140.         chop;
  141.         
  142.         # Skip rest of input.
  143.         if( /^EXIT$/ ){
  144.             last;
  145.         }
  146.     
  147.         if( /^home\s*=\s*(\S+)/ ){
  148.             chdir( $1 ) || die "Cannot chdir to $1";
  149.             next;
  150.         }
  151.     
  152.         if( /^max\s*=\s*(\d+)/ ){
  153.             # Set the max no. of parallel mirrors
  154.             $max = $1;
  155.             next;
  156.         }
  157.         
  158.         if( /^mirror\s*=\s*(.*)/ ){
  159.             # Set the mirror command
  160.             $mirror = $1;
  161.             next;
  162.         }
  163.         
  164.         if( /^cmd\s*=\s*(.*)/ ){
  165.             # Run this shell command now
  166.             # Use it at the start of scripts to do cleanups and
  167.             # at the end to email logs
  168.             # but first wait until all transfers are done
  169.             &wait_till_done( 0 );
  170.             system( $1 );
  171.             next;
  172.         }
  173.         
  174.         if( /^cmdin\s*=\s*(.*)/ ){
  175.             # Run this command and use its output as mm input
  176.             # (The trailing hash makes open treat it as a command.
  177.             &parse_file( $1 . '|' );
  178.             next;
  179.         }
  180.         
  181.         if( /^skip\s*=\s*(.*)/ ){
  182.             # Skip this site:package
  183.             push( @skips, $1 );
  184.             next;
  185.         }
  186.     
  187.         # Must be a job to run
  188.         # site:package min-restart-last-ok min-restart-last-notok mirror-args
  189.         if( /^(.+):(.+)\s+(\d+)\s+(\d+)(\s*)?(.*)?/ ){
  190.             local( $site, $package, $min_ok, $min_notok, $args )
  191.                 = ($1, $2, $3, $4, $6);
  192.             $pkg = &fix_package( $package );
  193.             local( $site_package ) = "$site:$package";
  194.     
  195.             if( $site_package =~ /'/ ){
  196.                 warn "site/package name must not contain a prime ('), skipping: $site:$package\n";
  197.                 next;
  198.             }
  199.             
  200.             # Is this a skipped site?
  201.             if( grep( /$site_package/, @skips ) ){
  202.                 print "skipping $site_package, in skip list\n" if( $debug );
  203.                 next;
  204.             }
  205.     
  206.             # If restricting the packages to look at skip all that
  207.             # don't match.
  208.             if( $only && $site_package !~ /$only/ ){
  209.                 print "skipping $site_package, not in $only\n" if( $debug );
  210.                 next;
  211.             }
  212.     
  213.             # Only try the first instance of a site:package found.
  214.             next if $already{ $site_package };
  215.             $already{ $site_package } = 1;
  216.             
  217.             if( ! &ok_to_restart( $site_package, $min_ok, $min_notok ) ){
  218.                 next;
  219.             }
  220.             local( $command ) = "$mirror";
  221.             local( $a ) = "$args $extra_args";
  222.             $command =~ s/\$args/$a/g;
  223.             $command =~ s/\$site/$site/g;
  224.             $command =~ s/\$package/$package/g;
  225.             $command =~ s/\$pkg/$pkg/g;
  226.             &run( $command, $site_package );
  227.             next;
  228.         }
  229.         else {
  230.             warn "Cannot parse, so skipping: $_\n";
  231.         }
  232.     }
  233.     
  234.     if( $closeit ){
  235.         close( $fd );
  236.     }
  237. }
  238.  
  239. sub run
  240. {
  241.     local( $command, $site_package ) = @_;
  242.     
  243.     if( $running >= $max ){
  244.         &wait_till_done( 1 );
  245.     }
  246.  
  247.     local( $pid ) = &spawn( $command );
  248.     $running ++;
  249.     $procs{ $pid } = $site_package;
  250.     print "$pid: $procs{ $pid } started: $command\n" if $debug;
  251.     &upd_status( $site_package, time, 0, $locked, $pid );
  252. }
  253.  
  254. sub spawn
  255. {
  256.     local( $command ) = @_;
  257.     local( $id ) = fork();
  258.     
  259.     if( $id == 0 ){
  260.         # This is the child
  261.         exec( $command );
  262.         die "Failed to exec $command";
  263.     }
  264.     elsif( $id > 0 ){
  265.         # This is the parent
  266.         return $id;
  267.     }
  268.     
  269.     die "Failed to fork";
  270.     # Should really sleep and try again...
  271. }
  272.  
  273. sub wait_till_done
  274. {
  275.     local( $children ) = @_;
  276.     local( $pid );
  277.     
  278.     if( $children == 0 ){
  279.         # Wait for all remaining children
  280.         while( ($pid = wait()) != -1 ){
  281.             &proc_end( $pid, $? );
  282.         }
  283.     }
  284.     else {
  285.         # Wait for the next child to finish
  286.         while( 1 ){
  287.             $pid = wait();
  288.             if( $pid == -1 ){
  289.                 die "Waiting for NO children";
  290.             }
  291.             last if &proc_end( $pid, $? );
  292.         }
  293.     }
  294. }
  295.  
  296. # A process has terminate.   Figure out which one and update the status file
  297. # If a real child has ended then return 1 else 0.
  298. sub proc_end
  299. {
  300.     local( $pid, $status ) = @_;
  301.     local( $site_package ) = $procs{ $pid };
  302.     
  303.     if( $site_package !~ /(.+):(.+)/ ){
  304.         # Ignore these.  It is probably just an open(..,"..|)
  305.         # terminating.  They seem to do it at odd times!
  306.         return 0;
  307.     }
  308.     
  309.     print "$pid: $site_package terminated[$status]\n" if $debug;
  310.     $running --;
  311.  
  312.     &upd_status( $site_package, time, $status, $unlocked );
  313.     return 1;
  314. }
  315.  
  316. sub ok_to_restart
  317. {
  318.     local( $site_package, $min_ok, $min_notok ) = @_;
  319.     
  320.     local( $last_tried, $status, $lock, $pid ) = &get_status( $site_package );
  321.     
  322.     if( $lock eq $locked ){
  323.         # Does the process that locked it still exist?
  324.         if( kill( 0, $pid ) ){
  325.             warn "Not trying $site_package: locked by $pid\n";
  326.             return 0;
  327.         }
  328.     }
  329.     
  330.     if( $ignore_timers ){
  331.         return 1;
  332.     }
  333.  
  334.     $min_ok = $min_ok * $secs_per_hour;
  335.     $min_notok = $min_notok * $secs_per_hour;
  336.     
  337.     local( $min ) = $min_notok;
  338.     if( $status == $exit_ok ){
  339.         $min = $min_ok;
  340.     }
  341.  
  342.     local( $now ) = time;
  343.     local( $togo ) = ($last_tried + $min) - $now;
  344.     if( $last_tried && $togo > 0 ){
  345.         warn "Not trying $site_package: $togo seconds to go\n";
  346.         return 0;
  347.     }
  348.     
  349.     return 1;
  350. }
  351.  
  352. sub lock_status
  353. {
  354.     &myflock( $LOCK_EX );
  355. }
  356.  
  357. sub unlock_status
  358. {
  359.     &myflock( $LOCK_UN );
  360. }
  361.  
  362. sub myflock
  363. {
  364.     local( $kind ) = @_;
  365.  
  366.     if( ! $can_flock ){
  367.         return;
  368.     }
  369.  
  370.     eval( "flock( status, $kind )" );
  371.     if( $@ =~ /unimplemented/ ){
  372.         $can_flock = 0;
  373.         warn "flock not unavialable, running unlocked\n";
  374.     }
  375. }    
  376.  
  377. # Update the status file
  378. sub upd_status
  379. {
  380.     local( $site_package, $last_tried, $status, $lock, $pid ) = @_;
  381.     
  382.     # Make sure a status file exists
  383.     if( ! -e $status_file ){
  384.         open( status, ">$status_file" ) || die "Cannot create $status_file";
  385.         close( status );
  386.     }
  387.  
  388.     # Suck in the status file
  389.     open( status, '+<' . $status_file ) || die "Cannot open $status_file";
  390.     &lock_status();
  391.     seek( status, 0, 0 );
  392.     $upd = 0;
  393.     local( @new ) = ();
  394.     while( <status> ){
  395.         if( /^(.+:.+)\s+(\d+)\s+(\d+)\s+($locked|$unlocked)\S?\s+(\d+)$/ ){
  396.             local( $sp, $lt, $st, $lk, $p ) =
  397.                 ($1, $2, $3, $4, $5);
  398.             if( $sp eq $site_package ){
  399.                 print "upd: $_" if( $status_debug );
  400.                 if( $last_tried ){
  401.                     $lt = $last_tried;
  402.                 }
  403.                 if( $status ){
  404.                     $st = $status;
  405.                 }
  406.                 if( $lock ){
  407.                     $lk = $lock;
  408.                 }
  409.                 if( $pid > 0 ){
  410.                     $p = $pid;
  411.                 }
  412.                 $upd++;
  413.                 push( @new, "$sp $lt $st $lk $p\n" );
  414.                 print "$sp $lt $st $lk $p\n" if( $status_debug );
  415.                 next;
  416.             }
  417.             push( @new, $_ );
  418.         }
  419.         elsif( /^\s*$/ ){
  420.             last;
  421.         }
  422.         else {
  423. #            warn "Unknown input skipping rest of file, $status_file:$.: $_\n";
  424.             last;
  425.         }
  426.     }
  427.     if( ! $upd ){
  428.         local( $new ) = "$site_package $last_tried $status $lock $pid\n";
  429.         push( @new, $new );
  430.         print "new: $new" if( $status_debug );
  431.     }
  432.     seek( status, 0, 0 );
  433.     foreach $new ( @new ){
  434.         print status $new;
  435.     }
  436.     # Get rid of the rest of the file.
  437.     eval "truncate( status, tell( status ) )";
  438.     
  439.     &unlock_status();
  440.     close( status );
  441. }
  442.  
  443.  
  444. # Get the status of a site:package
  445. sub get_status
  446. {
  447.     local( $site_package ) = @_;
  448.     local( $last_tried, $status, $lock, $pid ) = (0, 0, ' ', -1);
  449.     
  450.     # Make sure a status file exists
  451.     if( ! -e $status_file ){
  452.         open( status, ">$status_file" ) || die "Cannot create $status_file";
  453.         close( status );
  454.     }
  455.  
  456.     # Suck in the status file
  457.     open( status, '+<' . $status_file ) || die "Cannot open $status_file";
  458.     &lock_status();
  459.     seek( status, 0, 0 );
  460.     local( @new ) = ();
  461.     while( <status> ){
  462.         if( /^(.+:.+)\s+(\d+)\s+(\d+)\s+($locked|$unlocked)\S?\s+(\d+)$/ ){
  463.             local( $sp, $lt, $st, $lk, $p ) =
  464.                 ($1, $2, $3, $4, $5);
  465.             if( $sp eq $site_package ){
  466.                 $last_tried = $lt;
  467.                 $status = $st;
  468.                 $lock = $lk;
  469.                 $pid = $p;
  470.                 if( $lock eq $locked && ! &still_running( $pid ) ){
  471.                     print "unlocking $_";
  472.                     $lock = $unlocked;
  473.                 }
  474.                 print "Status: $_" if( $status_debug );
  475.                 last;
  476.             }
  477.         }
  478.         else {
  479.             warn "Unknown input skipping rest of file, $status_file:$.: $_\n";
  480.             last;
  481.         }
  482.     }
  483.     &unlock_status();
  484.     close( status );
  485.     return( $last_tried, $status, $lock, $pid );
  486. }
  487.  
  488. # Fix up a package name.
  489. # strip trailing and leading ws and replace awkward characters
  490. sub fix_package
  491. {
  492.     local( $package ) = @_;
  493.     $package =~ s:[\s/']:_:g;
  494.     return $package;
  495. }
  496.  
  497. # Return true if the process is still running.
  498. sub still_running
  499. {
  500.     local( $pid ) = @_;
  501.     
  502.     return (kill 0, $pid) != 0;
  503. }
  504.